package com.beta.cust.bind.datasync.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.beta.cat.service.RedissonService;
import com.beta.cat.utils.GJsonUtil;
import com.beta.cust.bind.datasync.config.CfgSelfRefreshBean;
import com.beta.cust.bind.datasync.constant.BusinessConstant;
import com.beta.cust.bind.datasync.constant.RedisConstant;
import com.beta.cust.bind.datasync.constant.WeixinContants;
import com.beta.cust.bind.datasync.constant.WxWorkUrlContants;
import com.beta.cust.bind.datasync.dto.*;
import com.beta.cust.bind.datasync.http.HttpService;
import com.beta.cust.bind.datasync.mapper.entity.AuthCorpInfo;
import com.beta.cust.bind.datasync.service.IAccessTokenService;
import com.beta.cust.bind.datasync.service.IWechatWorkService;
import com.beta.cust.bind.datasync.service.wechat.SyncWechatInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

/**
 * @Author：chenlei
 * @Date：2024/3/13 19:02
 */
@Service
@Slf4j
public class WechatWorkServiceImpl implements IWechatWorkService {

    @Autowired
    private HttpService httpService;

    @Autowired
    private IAccessTokenService iAccessTokenService;
    @Resource
    private CfgSelfRefreshBean cfgSelfRefreshBean;

    @Autowired
    private RedissonService redissonService;

    @Autowired
    private AuthCorpInfoServiceImpl corpInfoService;

    @Autowired
    private SyncWechatWorkDepartmentImpl syncWechatWorkDepartment;

    @Autowired
    private SyncWechatWorkEmployeeInfoImpl syncWechatWorkEmployeeInfo;

    @Autowired
    private SyncWechatWorkCustInfoImpl syncWechatWorkCustInfo;

    @Resource
    private SyncWechatInfo syncWechatInfo;

    @Override
    public WeixinCorpListDTO corpList(boolean again, String corpId, String cursor) {
        // 获取access_token
        String accessToken = iAccessTokenService.getAccessToken(corpId, cfgSelfRefreshBean.getCorpSecret());//获取下游企业信息时，传的参数是上游的信息。

        // 调用企业微信获取部门列表
        String url = WxWorkUrlContants.LIST_APP_SHARE_INFO + accessToken;
        Map<String, Object> param = new HashMap<>();
        param.put("agentid", cfgSelfRefreshBean.getAgentId());
        param.put("business_type", 1);
        param.put("limit", cfgSelfRefreshBean.getCorpLimit());
        if (StringUtils.isNotBlank(cursor)) {
            param.put("cursor", cursor);
        }
        String result = httpService.post(url, GJsonUtil.toJson(param));
        WeixinCorpListDTO dto = JSONObject.parseObject(result, WeixinCorpListDTO.class);

        if (Objects.isNull(dto) || dto.getErrcode().intValue() != WeixinContants.SUCCESS_CODE) {
            if (!again) {
                corpList(true, corpId, cursor);
            }
            return null;
        }
        return dto;
    }

    /**
     * 获取部门列表<br>
     * 详情见：https://developer.work.weixin.qq.com/document/path/90208
     */
    @Override
    public WeixinDepListDTO departmentList(boolean again, String corpId, String agentid) {
        // 获取access_token
        String accessToken = getAccessTokenFromWxwork(corpId, agentid);

        // 调用企业微信获取部门列表
        String url = WxWorkUrlContants.GET_DEP_LIST_URL + accessToken;
        String result = httpService.get(url, null);
        WeixinDepListDTO departmentDTO = JSONObject.parseObject(result, WeixinDepListDTO.class);
        if (Objects.isNull(departmentDTO) || departmentDTO.getErrcode().intValue() != 0) {
            if (!again) {
                // 再试一次
                return departmentList(true, corpId, agentid);
            }
            log.error("获取部门列表异常:{}", GJsonUtil.toJson(departmentDTO));
        }
        return departmentDTO;
    }

    @Override
    public WeixinDepListDTO.DepartmentDetailDTO departmentDetail(Long departmentId, String corpId, String agentId) {
        String accessToken = getAccessTokenFromWxwork(corpId, agentId);
        // 调用企业微信获取部门列表
        String url = WxWorkUrlContants.GET_DEP_LDETAIL_URL + accessToken + "&id=" + departmentId;
        String result = httpService.get(url, null);
        WeixinDepDetailDto depDetail = JSONObject.parseObject(result, WeixinDepDetailDto.class);
        if (Objects.isNull(depDetail) || depDetail.getErrcode().intValue() != 0) {
            log.error("获取部门详情异常:{}", GJsonUtil.toJson(depDetail));
        }
        return depDetail.getDepartment();
    }


    /**
     * 获取部门成员详情<br>
     * 详情见：https://work.weixin.qq.com/api/doc/90000/90135/90201<br>
     *
     * @param departmentId -> 企业id -> 是否必须：是
     * @param again        -> 重试 -> true表示重试，flase表示不重试 -> 是否必须：是
     * @param corpId       -> 企业id -> 是否必须：是
     */
    @Override
    public PersonnelDTO employeeDetailList(boolean again, Long departmentId, String corpId, String agentid) {
        String accessToken = getAccessTokenFromWxwork(corpId, agentid);

        Map<String, Object> param = new HashMap<>();
        param.put("access_token", accessToken);
        param.put("department_id", departmentId);
        param.put("fetch_child", 0);
        String result = httpService.get(WxWorkUrlContants.USER_SIMPLELIST_URL, param);
        PersonnelDTO personnelDTO = JSONObject.parseObject(result, PersonnelDTO.class);
        if (Objects.isNull(personnelDTO) || personnelDTO.getErrcode().intValue() != 0) {
            if (!again) {
                employeeDetailList(true, departmentId, corpId, agentid);
            }
            return null;
        }
        return personnelDTO;
    }

    @Override
    public WeixinUserDetailDto employeeDetail(String userID, String corpId, String agentId) {
        String accessToken = getAccessTokenFromWxwork(corpId, agentId);

        // 调用企业微信获取部门列表
        String url = WxWorkUrlContants.GET_EMPLOYEE_LDETAIL_URL + accessToken + "&userid=" + userID;
        String result = httpService.get(url, null);
        WeixinUserDetailDto userDetail = JSONObject.parseObject(result, WeixinUserDetailDto.class);
        if (Objects.isNull(userDetail) || userDetail.getErrcode().intValue() != 0) {
            log.error("获取员工详情异常:{}", GJsonUtil.toJson(userDetail));
        }
        return userDetail;
    }

    @Override
    public ExternalcontactByUserIdDTO queryCustInfos(boolean again, String userId, String corpId, String agentid, String cursor) {
        // 获取access_token
        String accessToken = getAccessTokenFromWxwork(corpId, agentid);
        String url = WxWorkUrlContants.EXTERNALCONTACT_BATCH_GET_BY_USER_URL + accessToken;
        Map<String, Object> param = new HashMap<>();
        param.put("userid_list", Collections.singletonList(userId));
        param.put("limit", cfgSelfRefreshBean.getCorpLimit());
        if (StringUtils.isNotBlank(cursor)) {
            param.put("cursor", cursor);
        }
        String result = httpService.post(url, GJsonUtil.toJson(param));
        ExternalcontactByUserIdDTO dto = JSONObject.parseObject(result, ExternalcontactByUserIdDTO.class);
        if (Objects.isNull(dto) || dto.getErrcode().intValue() != 0) {
            if (!again) {
                queryCustInfos(true, userId, corpId, agentid, cursor);
            }
            return null;
        }
        return dto;
    }

    /**
     * 获取客户详情
     * 详情见：https://work.weixin.qq.com/api/doc/90000/90135/92114
     */
    @Override
    public ExternalCustInfoDTO getExternalCustInfo(String externalUserId, String corpId, String agentId) {
        // 获取access_token
        String accessToken = getAccessTokenFromWxwork(corpId, agentId);

        Map<String, Object> param = new HashMap<>();
        param.put("access_token", accessToken);
        param.put("external_userid", externalUserId);
        String result = httpService.get(WxWorkUrlContants.EXTERNALCONTACT_URL, param);
        ExternalCustInfoDTO externalcontactDTO = JSONObject.parseObject(result, ExternalCustInfoDTO.class);
        return externalcontactDTO;
    }

    private String getAccessTokenFromWxwork(String corpId, String agentid) {
        String accessToken;
        if (StringUtils.isNotBlank(agentid)) {
            accessToken = iAccessTokenService.getCorpToken(corpId, agentid);
        } else {
            accessToken = iAccessTokenService.getAccessToken(corpId, cfgSelfRefreshBean.getCorpSecret());
        }
        return accessToken;
    }

    /**
     * 批量获取客户详情
     *
     * @param userId
     * @param externalContactList
     * @param nextCursor
     * @return
     */
    private List<ExternalcontactByUserIdDTO.ExternalContactListDTO> getDataMethod(String userId, String enterpriseId, List<ExternalcontactByUserIdDTO.ExternalContactListDTO> externalContactList, int count, String nextCursor) {
        ExternalcontactByUserIdDTO externalcontactByUserIdDTO = getExternalcontactByUserIdDTO(userId, enterpriseId, nextCursor);

        if (Objects.isNull(externalcontactByUserIdDTO) || externalcontactByUserIdDTO.getErrcode().intValue() != 0) {
            // 有一次补偿机制, 这里需要考虑第N次同步成功了 第N+1次同步失败，此时需要接着第N次后面继续同步，所以nextCursor需要有第N次的值，首次不需要这样的考虑
            if (count >= 1) {
                log.info("企业{}已经没有补偿机制次数了，被使用完了", enterpriseId);
            } else {
                log.info("企业{}正在进行补偿机制", enterpriseId);
                count = 1;
                getDataMethod(userId, enterpriseId, externalContactList, count++, nextCursor);
            }
        } else {
            List<ExternalcontactByUserIdDTO.ExternalContactListDTO> externalContactList1 = externalcontactByUserIdDTO.getExternalContactList();
            if (!CollUtil.isEmpty(externalContactList1)) {
                externalContactList.addAll(externalContactList1);
                if (externalContactList1.size() == 100) {    // 每次最多获取一百个数据，如果正好时100个还需要再去调用看看还有没有数据
                    nextCursor = externalcontactByUserIdDTO.getNextCursor();
                    getDataMethod(userId, enterpriseId, externalContactList, count, nextCursor);
                }
            }
        }
        log.info("企业{}批量获取客户详情结束", enterpriseId);
        return externalContactList;
    }

    /**
     * 调用企业微信批量获取客户详情
     *
     * @param userId
     * @param nextCursor
     * @return
     */
    private ExternalcontactByUserIdDTO getExternalcontactByUserIdDTO(String userId, String enterpriseId, String nextCursor) {
        String accessToken = iAccessTokenService.getAccessToken(enterpriseId, cfgSelfRefreshBean.getCorpSecret());
        String url = WxWorkUrlContants.EXTERNALCONTACT_BATCH_GET_BY_USER_URL + accessToken;
        Map<String, Object> param = new HashMap<>();
        param.put("userid_list", Arrays.asList(userId));
        param.put("cursor", StringUtils.EMPTY);
        param.put("limit", 100);
        if (StringUtils.isNotBlank(nextCursor)) {
            param.put("cursor", nextCursor);
        }
        String result = httpService.post(url, JSONObject.toJSONString(param));
        //log.info("调用企业微信批量获取客户详情得到的数据是：{}", result);
        return JSONObject.parseObject(result, ExternalcontactByUserIdDTO.class);
    }


    @Override
    public void callBackEmplChange(JSONObject jo, String corpId) {
        //获取本次的 ToUserName 请求类型 用户ID 部门ID
        String toUserName = jo.getString("ToUserName");
        String changeType = jo.getString("ChangeType");
        String userID = jo.getString("UserID");
        Long deptId = jo.getLong("Id");

        //加锁，防止重复刷新数据
        String lockKey = String.format(RedisConstant.SYNC_CALL_BACK_LOCK_KEY, toUserName, changeType, StringUtils.isBlank(userID) ? deptId : userID);
        try {
            if (redissonService.tryLock(lockKey, RedisConstant.CALL_BACK_WAIT_TIME)) {
                //判断corpId是总公司则无需查询企业信息 下游企业则需要查询企业信息,如未查询到企业信息,则不执行此次回调
                String agentId = "";
                if (!toUserName.equals(cfgSelfRefreshBean.getCorpId())) {
                    agentId = corpInfoService.getAgentIdByCorpId(toUserName);
                    if (ObjectUtils.isEmpty(agentId)) {
                        log.warn("{}下游企业未同步至企业信息表中，此次回调不执行", toUserName);
                        return;
                    }
                }

                if (changeType.equals(BusinessConstant.CREATE_PARTY)) {
                    syncWechatWorkDepartment.createDepartMent(deptId, toUserName, agentId);
                } else if (changeType.equals(BusinessConstant.UPDATE_PARTY)) {
                    syncWechatWorkDepartment.updateDepartment(deptId, toUserName, agentId);
                } else if (changeType.equals(BusinessConstant.DELETE_PARTY)) {
                    syncWechatWorkDepartment.deleteDepartment(deptId, toUserName);
                } else if (changeType.equals(BusinessConstant.CREATE_USER)) {
                    syncWechatWorkEmployeeInfo.createEmployee(userID, toUserName, agentId);
                } else if (changeType.equals(BusinessConstant.UPDATE_USER)) {
                    syncWechatWorkEmployeeInfo.updateEmployee(userID, toUserName, agentId);
                } else if (changeType.equals(BusinessConstant.DELETE_USER)) {
                    syncWechatWorkEmployeeInfo.deleteEmployee(userID, toUserName);
                } else {
                    log.error("callBackEmplChange 企微用户部门回调业务类型匹配失败:{}", GJsonUtil.toJson(jo));

                }
            } else {
                log.warn("callBackEmplChange 企微用户部门回调运行中");
            }
        } catch (Exception e) {
            log.error("callBackEmplChange 企微用户部门回调执行出错:{},错误信息:{}", GJsonUtil.toJson(jo), e.getMessage(), e);

        } finally {
            redissonService.unlock(lockKey);
        }

    }

    @Override
    public void callBackCustChange(JSONObject jo, String corpId) {
        //获取ToUserName 业务类型 外部联系人的userid 员工ID
        String toUserName = jo.getString("ToUserName");
        String changeType = jo.getString("ChangeType");
        String externalUserID = jo.getString("ExternalUserID");
        String userID = jo.getString("UserID");
        //加锁，防止重复刷新数据
        String lockKey = String.format(RedisConstant.SYNC_CALL_BACK_LOCK_KEY, toUserName, changeType, externalUserID);
        try {
            if (redissonService.tryLock(lockKey, RedisConstant.CALL_BACK_WAIT_TIME)) {
                //判断corpId是总公司则无需查询企业信息 下游企业则需要查询企业信息,如未查询到企业信息,则不执行此次回调
                String agentId = "";
                if (!toUserName.equals(cfgSelfRefreshBean.getCorpId())) {
                    AuthCorpInfo corpInfo = corpInfoService.getOne(new LambdaQueryWrapper<AuthCorpInfo>()
                            .eq(AuthCorpInfo::getCorpId, toUserName)
                            .eq(AuthCorpInfo::getDelFlag, BusinessConstant.IS_NOT_DEL_FLAG));

                    if (ObjectUtils.isEmpty(corpInfo)) {
                        log.warn("{}下游企业未同步至企业信息表中，此次回调不执行", toUserName);
                        return;
                    }
                    agentId = corpInfo.getAgentId();
                }

                if (BusinessConstant.DEL_FOLLOW_USER.equals(changeType)) {
                    syncWechatWorkCustInfo.deleteCust(toUserName, externalUserID, userID);
                } else if (BusinessConstant.DEL_EXTERNAL_CONTACT.equals(changeType)) {
                    syncWechatWorkCustInfo.deleteCust(toUserName, externalUserID, userID);
                } else if (BusinessConstant.ADD_EXTERNAL_CONTACT.equals(changeType)) {
                    syncWechatWorkCustInfo.createCustInfo(userID, externalUserID, toUserName, agentId);
                } else if (BusinessConstant.CORP_JOIN.equals(changeType)
                        || BusinessConstant.UPDATE_JOIN.equals(changeType)
                        || BusinessConstant.REMOVE_JOIN.equals(changeType)) {
                    //上下游企业信息变更时，直接调用定时任务全量同步，有可能牵扯到部门员工用户的变动
                    syncWechatInfo.syncWechatInfoBatch(cfgSelfRefreshBean.getCorpId());
                } else {
                    log.info("SyncWechatWorkEmployeeInfoImpl handleChangeExternalContact do not handle changeType={}", changeType);
                }
            } else {
                log.warn("callBackEmplChange 企微用户部门回调运行中");
            }
        } catch (Exception e) {
            log.error("callBackEmplChange 企微用户部门回调执行出错:{},错误信息:{}", GJsonUtil.toJson(jo), e.getMessage(), e);

        } finally {
            redissonService.unlock(lockKey);
        }
    }

}
